﻿#region Header

/*
Remote views based on linq
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Sebarf.Diagnostics.Interfaces;
using Sebastof.Services.Interfaces;
using Utils;

#endregion

namespace InterLinq.Views {
	/// <summary>
	/// helpers for converting a generic view into a <see cref="TypedView"/>
	/// </summary>
	public static class GenericVieweHelper {
		/// <summary>
		/// converts a IEnumerable[GenericView] into a IEnumerable[TypedView]
		/// </summary>
		/// <typeparam name="View"></typeparam>
		/// <param name="list"></param>
		/// <returns></returns>
		public static IEnumerable<View> AsTypedCollectionOf<View>(this IEnumerable<GenericView> list) where View : TypedView {
			return (from entry in list select (AsTypedView<View>(entry))).ToArray();
		}

		/// <summary>
		/// converts a IEnumerable[GenericView] into a IEnumerable[TypedView]
		/// </summary>
		/// <typeparam name="View">has to have a public ctr</typeparam>
		/// <returns></returns>
		public static View AsTypedView<View>(this GenericView view) where View : TypedView {
			var toReturn = (Activator.CreateInstance(typeof(View)) as View);
			if (toReturn == null) {
				Check.AssertWarning(false, "has to use a view that has a public ctr");
				return null;
			}
			return toReturn.SetUsedGenericView<View>(view);
		}
	}

	/// <summary>
	/// Description of the class
	/// </summary>
	[Serializable]
	public class GenericView : SebastofApplicationObject {
		#region Public Properties

		/// <summary>
		/// Gets if the view is readonly
		/// </summary>
		public bool GetIsReadOnly(string key) {
			return m_readonlyFields.Contains(key);
		}

		public bool IsOnClient { get; private set; }

		#endregion

		#region Public Methods

		public GenericView() {
		}

		public GenericView(params InternalData[] data) {
			foreach (InternalData entry in data) {
				AddEntryFromDatasourceToView(entry.DataSource, entry.PropertiesToAdd);
			}
		}

		public GenericView(params Object[] data) {
			AddEntriesToView(data);
		}

		public GenericView AddEntryToView(string key, object value) {
			m_readonlyFields.Add(key);
			SetValue(key, value);
			AsReadOnly(false);
			return this;
		}

		public GenericView AddEntryToView<ObjectType>(string key, ObjectType value) {
			return AddEntryToView(key, value);
		}

		public GenericView AddEntriesToView(params object[] keyValueArgs) {
			bool rightUsage = keyValueArgs.Length % 2 == 0;
			Check.AssertWarning(rightUsage, "key and value has to be submitt");
			if (!rightUsage) {
				return this;
			}
			for (int i = 0; i < keyValueArgs.Length; i += 2) {
				AddEntryToView((string)keyValueArgs[i], keyValueArgs[i + 1]);
			}
			return this;
		}

		public GenericView AddEntryFromDatasourceToView(object source, params string[] propertiesToAdd) {

			foreach (string property in propertiesToAdd) {
				string name = property;
				string alias = name;
				if (property.Contains(" as ")) {
					// is an alias
					alias = name.Split(' ').Last();
					name = name.Split(' ').First();
				}
				Check.AssertDebug(!m_propertyAliasToPropertyNameMap.ContainsKey(alias), "property always used");
				if (!(source is ISebastofApplicationObject && source is IVersionedObject)) {
					m_readonlyFields.Add(alias);

				}
				m_propertyAliasToPropertyNameMap.Add(alias, name);
				string id = Guid.NewGuid().ToString();
				if (source is IIdentifiable) {
					id = (source as IIdentifiable).Id;
				}
				m_objectContainerByPropertyAlias.Add(alias, id);
				m_typeByObjectContainer.Add(id, source.GetType());
				object version = (source as IVersionedObject) == null
									? null
									: (source as IVersionedObject).GetVersion();
				m_versionByObjectContainer.Add(id, version);

				object value = null;
				if ((source is ISebastofApplicationObject)) {
					value = (source as ISebastofApplicationObject).GetValue(name);
				}
				else {
					// find by property
					PropertyInfo propertyField =
						(from p in source.GetType().GetProperties() where p.Name == name select p).FirstOrDefault();
					if (propertyField != null && propertyField.CanRead) {
						value = propertyField.GetValue(source, null);
						if (!propertyField.CanWrite) {
							m_readonlyFields.Add(alias);
						}
					}
					else {
						FieldInfo field = (from p in source.GetType().GetFields() where p.Name == name select p).FirstOrDefault();
						if (field != null) {
							value = field.GetValue(source);
						}
						else {
							Logger.WriteWarning(name + " is not available on the given object");
						}
					}
				}
				SetValue(alias, value);

			}
			// clean up

			AsReadOnly(false);

			return this;
		}

		public GenericView AsReadOnly() {
			return AsReadOnly(true);
		}


		public GenericView AsReadOnly(bool forceReadOnly) {
			if (forceReadOnly) {
				m_objectContainerByPropertyAlias.Clear();
				m_propertyAliasToPropertyNameMap.Clear();
				m_typeByObjectContainer.Clear();
				m_versionByObjectContainer.Clear();
				m_readonlyFields.Clear();
				m_readonlyFields.AddRange((from v in GetValues() select v.Key).ToArray());
				return this;
			}
			foreach (String readonlyField in m_readonlyFields) {
				if (m_propertyAliasToPropertyNameMap.ContainsKey(readonlyField)) {
					var objectContainer = m_objectContainerByPropertyAlias[readonlyField];
					// clean unused entries
					m_objectContainerByPropertyAlias.Remove(readonlyField);
					m_propertyAliasToPropertyNameMap.Remove(readonlyField);
					m_typeByObjectContainer.Remove(objectContainer);
					m_versionByObjectContainer.Remove(objectContainer);
				}
			}
			return this;
		}

		/// <summary>
		/// <see cref="SebastofApplicationObject.SetValue"/>
		/// </summary>
		/// <exception cref="NotSupportedException"></exception>
		/// <param name="key"></param>
		/// <param name="value"></param>
		public override void SetValue(string key, object value) {
			bool isReadOnly = GetIsReadOnly(key);
			if (isReadOnly && IsOnClient) {
				throw new NotSupportedException("is readonly");
			}
			bool isDifferent = UsedDataHandlingStrategy.IsDifferent(key, value, this);
			UsedDataHandlingStrategy.SetValue(key, value, this);

			if (!isReadOnly) {
				foreach (IAppliedChangesOnObjectsCollectorService observer in GetObserver()) {
					string objectKeyId = m_objectContainerByPropertyAlias[key];
					observer.DepositeValueChangedByObjectIdEntry(m_propertyAliasToPropertyNameMap[key], value, objectKeyId,
																  m_typeByObjectContainer[objectKeyId],
																  m_versionByObjectContainer[objectKeyId]);
				}
				if (isDifferent) {
					ThrowPropertyChanged(key);
				}
				string objectKey = m_objectContainerByPropertyAlias[key];

				if (AttachedServiceLocatorService != null &&
					AttachedServiceLocatorService.GetRegisteredService<ICachedDataLocatorService>().ContainsKey(objectKey)) {
					AttachedServiceLocatorService.GetRegisteredService<ICachedDataLocatorService>().GetCachedData
						<ISebastofApplicationObject>(objectKey).SetValue(m_propertyAliasToPropertyNameMap[key], value);
				}
			}
		}

		public override T GetValue<T>(string key) {
			if (GetIsReadOnly(key)) {
				return base.GetValue<T>(key);
			}
			string objectKey = m_objectContainerByPropertyAlias[key];
			if (AttachedServiceLocatorService.GetRegisteredService<ICachedDataLocatorService>().ContainsKey(objectKey)) {
				return
					AttachedServiceLocatorService.GetRegisteredService<ICachedDataLocatorService>().GetCachedData
						<ISebastofApplicationObject>(objectKey).GetValue<T>(m_propertyAliasToPropertyNameMap[key]);
			}
			return base.GetValue<T>(key);
		}

		public override object GetValue(string key) {
			if (GetIsReadOnly(key)) {
				return base.GetValue(key);
			}
			string objectKey = m_objectContainerByPropertyAlias[key];
			if (AttachedServiceLocatorService.GetRegisteredService<ICachedDataLocatorService>().ContainsKey(objectKey)) {
				return
					AttachedServiceLocatorService.GetRegisteredService<ICachedDataLocatorService>().GetCachedData
						<ISebastofApplicationObject>(objectKey).GetValue(m_propertyAliasToPropertyNameMap[key]);
			}
			return base.GetValue(key);
		}

		protected override void OnRestoring() {
			var values = GetValues();
			UsedDataHandlingStrategy = new StandardDataHandlingStrategy(this);
			foreach (var entry in values) {
				SetValue(entry.Key, entry.Value);
			}
			IsOnClient = true;
		}

		#endregion

		#region Private Methods

		private readonly Dictionary<string, string> m_objectContainerByPropertyAlias = new Dictionary<string, string>();
		private readonly Dictionary<string, string> m_propertyAliasToPropertyNameMap = new Dictionary<string, string>();
		private readonly Dictionary<string, Type> m_typeByObjectContainer = new Dictionary<string, Type>();
		private readonly Dictionary<string, Object> m_versionByObjectContainer = new Dictionary<string, Object>();
		private readonly List<String> m_readonlyFields = new List<string>();
		#endregion
	}

	#region Public Classes

	[Serializable]
	public struct InternalData {
		public InternalData(object source, params string[] propertiesToAdd)
			: this() {
			DataSource = source;
			PropertiesToAdd = propertiesToAdd;
		}

		public Object DataSource { get; set; }
		public String[] PropertiesToAdd { get; set; }
	}

	#endregion
}